package com.pinfly.common.config;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;

import com.pinfly.common.util.StringUtil;

/**
 * Configuration properties
 * 
 * This class extends Java Properties by adding some utility methods for
 * formatting and working with the property values. The CfgPropertyFactory class
 * should be used to obtain the CfgProperties instances from the proper
 * implementation whether that is a file system or the Enterprise Configuration
 * System.
 * 
 */
public class PfyProperties extends Properties
{
    private static final long serialVersionUID = 1L;
    private static final Logger logger = Logger.getLogger (PfyProperties.class);

    private String m_group = null;

    /**
     * Construct an empty CxxProperties class for the specified group.
     * 
     * @param group
     */
    public PfyProperties (String group)
    {
        super ();
        m_group = group;
    }

    /**
     * Construct a CxxProperties class for the specified group and populate it
     * with the specified properties.
     * 
     * @param group
     * @param prop
     */
    public PfyProperties (String group, Properties prop)
    {
        super (prop);
        m_group = group;
    }

    /**
     * Add the name value pairs in the specified string array as properties.
     * 
     * @param namevalues
     */
    public void load (String[] namevalues)
    {
        if (namevalues == null)
        {
            throw new IllegalArgumentException ("namevalues cannot be null");
        }

        if ((namevalues.length % 2) != 0)
        {
            throw new IllegalArgumentException ("name/value mismatch");
        }

        for (int i = 0; i < namevalues.length; i = i + 2)
        {
            put (namevalues[i], namevalues[i + 1]);
        }

    }

    /**
     * Returns the group name these properties represent.
     * 
     * @return Group Name
     */
    public String getGroupName ()
    {
        return m_group;
    }

    /**
     * To retrieve a property value of type int
     * 
     * @param name The property name
     * @return property value
     * @throws ConfigException if the property value is NOT a int type or
     *             doesn't exist.
     */
    public int getIntProperty (String name) throws ConfigException
    {

        if (StringUtil.isNullOrBlank (name))
        {
            throw new IllegalArgumentException ("name cannot be null or empty string");
        }

        String value = (String) get (name);

        if (value == null)
        {
            throw new ConfigException ("The property of (" + name + ") does not exist.");
        }

        try
        {
            return Integer.parseInt (value);
        }
        catch (NumberFormatException ne)
        {
            throw new ConfigException ("The property of (" + name + ") is not a int type (" + value + ").");
        }
    }

    /**
     * To retrieve a property value of type int
     * 
     * @param name The property name
     * @param defaultValue
     * @return property value
     */
    public int getIntProperty (String name, int defaultValue)
    {

        if (StringUtil.isNullOrBlank (name))
        {
            throw new IllegalArgumentException ("name cannot be null or empty string");
        }

        String value = (String) get (name);

        if (value == null)
        {
            logger.debug ("The property of (" + name + ") does not exist, using default value of (" + defaultValue
                          + ").");
            return defaultValue;
        }

        try
        {
            return Integer.parseInt (value);
        }
        catch (NumberFormatException ne)
        {
            logger.debug ("The property of (" + name + ") is not a int type (" + value + "), using default value of ("
                          + defaultValue + ").");
            return defaultValue;
        }
    }

    /**
     * To retrieve a property value of type long
     * 
     * @param name The name of the property
     * @return property value
     * @throws ConfigException if the property value is NOT a long type or
     *             doesn't exist.
     */
    public long getLongProperty (String name) throws ConfigException
    {
        if (StringUtil.isNullOrBlank (name))
        {
            throw new IllegalArgumentException ("name cannot be null or empty string");
        }

        String value = (String) get (name);

        if (value == null)
        {
            throw new ConfigException ("The property of (" + name + ") does not exist.");
        }

        try
        {
            return Long.parseLong (value);
        }
        catch (NumberFormatException ne)
        {
            throw new ConfigException ("The property of (" + name + ") is not a long type (" + value + ").");
        }
    }

    /**
     * To retrieve a property value of type long
     * 
     * @param name The name of the property
     * @param defaultValue
     * @return property value
     */
    public long getLongProperty (String name, long defaultValue)
    {
        if (StringUtil.isNullOrBlank (name))
        {
            throw new IllegalArgumentException ("name cannot be null or empty string");
        }

        String value = (String) get (name);

        if (value == null)
        {
            logger.debug ("The property of (" + name + ") does not exist, using default value of (" + defaultValue
                          + ").");
            return defaultValue;
        }

        try
        {
            return Long.parseLong (value);
        }
        catch (NumberFormatException ne)
        {
            logger.debug ("The property of (" + name + ") is not a long type (" + value + "), using default value of ("
                          + defaultValue + ").");
            return defaultValue;
        }
    }

    /**
     * To retrieve a property value of boolean type
     * 
     * @param name The property name
     * @return property value
     * @throws ConfigException if the property value is NOT a boolean type or
     *             doesn't exist.
     */
    public boolean getBooleanProperty (String name) throws ConfigException
    {
        if (StringUtil.isNullOrBlank (name))
        {
            throw new IllegalArgumentException ("name cannot be null or empty string");
        }

        String value = (String) get (name);

        if (value == null)
        {
            throw new ConfigException ("The property of (" + name + ") does not exist.");
        }

        try
        {
            return Boolean.parseBoolean (value);
        }
        catch (NumberFormatException ne)
        {
            throw new ConfigException ("The property of (" + name + ") is not a boolean type (" + value + ").");
        }
    }

    /**
     * To retrieve a property value of boolean type
     * 
     * @param name The property name
     * @param defaultValue
     * @return property value
     */
    public boolean getBooleanProperty (String name, boolean defaultValue) throws ConfigException
    {
        if (StringUtil.isNullOrBlank (name))
        {
            throw new IllegalArgumentException ("name cannot be null or empty string");
        }

        String value = (String) get (name);

        if (value == null)
        {
            logger.debug ("The property of (" + name + ") does not exist, using default value of (" + defaultValue
                          + ").");
            return defaultValue;
        }

        try
        {
            return Boolean.parseBoolean (value);
        }
        catch (NumberFormatException ne)
        {
            logger.debug ("The property of (" + name + ") is not a boolean type (" + value
                          + "), using default value of (" + defaultValue + ").");
            return defaultValue;
        }
    }

    /**
     * Get a subProperty map. Finds all properties beginning with a prefix.
     * Stores these in a new Properties object with keys that have the prefix
     * removed. For example, 'User.name' will be stored using the key 'name'
     * when the input prefix argument is 'User.'.
     * 
     * @param prefix The property name prefix to search for
     * @return Properties collection with child properties of the parent key
     *         prefix
     */
    public Properties getSubProperties (String prefix)
    {
        if (prefix == null)
        {
            throw new IllegalArgumentException ("prefix cannot be null");
        }

        int prefixLength = prefix.length ();

        // create sub-Properties that will be returned
        Properties subProperties = new Properties ();

        // get the key names in this property set
        for (Enumeration keys = propertyNames (); keys.hasMoreElements ();)
        {
            String key = (String) keys.nextElement ();
            if (key.startsWith (prefix))
            {
                String value = getProperty (key);
                subProperties.setProperty (key.substring (prefixLength), value);
            }
        }

        return (subProperties);
    }

    /**
     * To retrieve a property value
     * 
     * @param name The property name
     * @param defaultValue
     * @return property value
     */
    public String getProperty (String name, String defaultValue)
    {

        if (StringUtil.isNullOrBlank (name))
        {
            throw new IllegalArgumentException ("name cannot be null or empty string");
        }

        String value = getProperty (name);

        if (value == null)
        {
            logger.debug ("The property of (" + name + ") does not exist, using default value of (" + defaultValue
                          + ").");
            return defaultValue;
        }

        return value;
    }

    /**
     * Get the entries in the map, sorted by key.
     * 
     * Returns a list of the entries in the map, sorted by key. (The sort is
     * case insensitive.) Note that unlike the entrySet() method, these entries
     * are not backed by the map, so that changes to the map are not reflected
     * in the list. The list represents a snapshot of the contents of the map.
     * 
     * @return A list of the entries in the map, sorted by key. Each item in the
     *         list is of type Map.
     */
    public List getSortedEntries ()
    {
        ArrayList list = new ArrayList (super.entrySet ());
        Collections.sort (list, new Comparator ()
        {
            public int compare (Object o1, Object o2)
            {
                String name1 = ((String) ((Map.Entry) o1).getKey ()).toLowerCase ();
                String name2 = ((String) ((Map.Entry) o2).getKey ()).toLowerCase ();
                return name1.compareTo (name2);
            }
        });
        return list;
    }

    /**
     * Format a property value.
     * 
     * Read a property value and use it as the input pattern to MessageFormat.
     * Apply the pattern to the supplied arguments.
     * 
     * This is a convenience function that combines the Properties.getProperty
     * method with the MessageFormat.format method.
     * 
     * @param propertyName The property whose value is to be used as the
     *            MessageFormat pattern.
     * @param arguments The arguments (usually strings) to apply to the pattern.
     * @return The formatted pattern
     */
    public String getFormattedProperty (String propertyName, Object[] arguments)
    {
        String prop = getProperty (propertyName);
        if (prop != null && arguments != null && arguments.length > 0)
        {
            MessageFormat format = new MessageFormat (prop);
            prop = format.format (arguments);
        }
        return prop;
    }

    /**
     * Format a property value.
     * 
     * Read a property value and use it as the input pattern to MessageFormat.
     * Apply the pattern to the supplied argument. This is a convenience
     * function to avoid the caller having to create an array of arguments. It
     * is exactly equivalent to getFormattedProperty (propertyName, new Object[]
     * {arg0, arg1, arg2})
     * 
     * @param propertyName The property whose value is to be used as the
     *            MessageFormat pattern.
     * @param arg0 The first argument (usually a string) to apply to the
     *            pattern.
     * @param arg1 The second argument (usually a string) to apply to the
     *            pattern.
     * @param arg2 The third argument (usually a string) to apply to the
     *            pattern.
     * @return The formatted pattern
     */
    public String getFormattedProperty (String propertyName, Object arg0, Object arg1, Object arg2)
    {
        return getFormattedProperty (propertyName, new Object[]
        { arg0, arg1, arg2 });
    }

    /**
     * Format a property value.
     * 
     * Read a property value and use it as the input pattern to MessageFormat.
     * Apply the pattern to the supplied argument. This is a convenience
     * function to avoid the caller having to create an array of arguments. It
     * is exactly equivalent to getFormattedProperty (propertyName, new Object[]
     * {arg0, arg1})
     * 
     * @param propertyName The property whose value is to be used as the
     *            MessageFormat pattern.
     * @param arg0 The first argument (usually a string) to apply to the
     *            pattern.
     * @param arg1 The second argument (usually a string) to apply to the
     *            pattern.
     * @return The formatted pattern
     */
    public String getFormattedProperty (String propertyName, Object arg0, Object arg1)
    {
        return getFormattedProperty (propertyName, new Object[]
        { arg0, arg1 });
    }

    /**
     * Format a property value.
     * 
     * Read a property value and use it as the input pattern to MessageFormat.
     * Apply the pattern to the supplied argument. This is a convenience
     * function to avoid the caller having to create an array of arguments. It
     * is exactly equivalent to getFormattedProperty (propertyName, new Object[]
     * {arg0})
     * 
     * @param propertyName The property whose value is to be used as the
     *            MessageFormat pattern.
     * @param arg0 The argument (usually a string) to apply to the pattern.
     * @return The formatted pattern
     */
    public String getFormattedProperty (String propertyName, Object arg0)
    {
        return getFormattedProperty (propertyName, new Object[]
        { arg0 });
    }
}
